#include "config.h"

#define DBG_SUBSYS S_LIBSTORAGE

#include "lich_api.h"

static root_t *__root__;

static int  __stor_root_create(const char *pool, const char *name, root_entry_t **_ent);
static int __stor_root_find_nolock(root_t *root, const char *pool, const char *name, fileid_t *rootid);
STATIC int __stor_root_load__(fileid_t *id, const char *pool, const char *path, int *new)
{
        int ret;
        setattr_t setattr;
        fileid_t parentid;
        char parent[MAX_NAME_LEN], name[MAX_NAME_LEN];
        chkinfo_t *chkinfo = mem_cache_calloc1(MEM_CACHE_4K, PAGE_SIZE);
        fileinfo_t fileinfo;

        if (strcmp(path, "/") == 0) {
                ret = md_root(id, pool);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = _path_split2(path, parent, name);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = __stor_root_load__(&parentid, pool, parent, new);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }

        retry:
                ret = md_lookup_byname(pool, &parentid, name, chkinfo, NULL);
                if (unlikely(ret)) {
                        if (ret == ENOENT) {
                                md_initattr(&setattr, __S_IFDIR, 0);

                                ret = md_getattr(&parentid, &fileinfo);
                                if (unlikely(ret)) {
                                        GOTO(err_ret, ret);
                                }

                                md_info2attr(&setattr, &fileinfo);

                                ret = md_mkpool(&parentid, name, &setattr, chkinfo);
                                if (unlikely(ret)) {
                                        if (ret == EEXIST) {
                                                goto retry;
                                        } else
                                                GOTO(err_ret, ret);
                                }

                                *new = 1;
                        } else
                                GOTO(err_ret, ret);
                }


                *id = chkinfo->id;
        }

        mem_cache_free(MEM_CACHE_4K, chkinfo);

        return 0;
err_ret:
        mem_cache_free(MEM_CACHE_4K, chkinfo);
        return _errno(ret);
}

STATIC int __stor_root_load(const char *pool, const char *name, fileid_t *_rootid)
{
        int ret, new = 0;
        fileid_t rootid;
        root_t *root = __root__;
        root_entry_t *ent;

        ANALYSIS_BEGIN(0);

        ret = __stor_root_create(pool, name, &ent);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __stor_root_load__(&rootid, pool, name, &new);
        if (unlikely(ret))
                GOTO(err_free, ret);

        ent->rootid = rootid;
        ent->inited = 1;
        *_rootid = rootid;

        ANALYSIS_END(0, 1000 * 100, NULL);

        // extend this root, to avoid no space when rmvol rmsnap
        if (new) {
                DINFO("%s -> %s "CHKID_FORMAT" extend\n", ent->pool, ent->name, CHKID_ARG(&rootid));
                ret = md_extend_pool(&rootid);
                if (unlikely(ret)) {
                        //TODO errcode
                        DWARN("%s -> %s "CHKID_FORMAT" extend fail\n", ent->pool, ent->name, CHKID_ARG(&rootid));
                }
        }

        ret = sy_rwlock_wrlock(&root->lock);
        if (unlikely(ret))
                GOTO(err_free, ret);

        ret = __stor_root_find_nolock(root, pool, name, &rootid);
        if (likely(ret)) {
                YASSERT(ret == ENOENT);
                list_add_tail(&ent->hook, &root->list);
        } else {
                YASSERT(!chkid_cmp(&rootid, &ent->rootid));
                yfree((void **)&ent);
        }

        sy_rwlock_unlock(&root->lock);

        return 0;
err_free:
        yfree((void **)&ent);
err_ret:
        return ret;
}

static int __stor_root_find_nolock(root_t *root, const char *pool, const char *name, fileid_t *rootid)
{
        int ret, found = 0;
        struct list_head *pos;
        root_entry_t *ent;

        list_for_each(pos, &root->list) {
                ent = (root_entry_t *)pos;

                if (!strcmp(ent->pool, pool) && !strcmp(ent->name, name)) {
                        found = 1;
                        break;
                }
        }

        if (found)
                *rootid = ent->rootid;
        else {
                ret = ENOENT;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __stor_root_find(root_t *root, const char *pool, const char *name, fileid_t *rootid)
{
        int ret;

        if (!root) {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

        ret = sy_rwlock_rdlock(&root->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret =  __stor_root_find_nolock(root, pool, name, rootid);
        if (unlikely(ret))
                GOTO(err_unlock, ret);

        sy_rwlock_unlock(&root->lock);

        return 0;
err_unlock:
        sy_rwlock_unlock(&root->lock);
err_ret:
        return ret;
}

static int  __stor_root_create(const char *pool, const char *name, root_entry_t **_ent)
{
        int ret;
        root_entry_t *ent;

        ret = ymalloc((void **)&ent, sizeof(*ent));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        memset(ent, 0x0, sizeof(*ent));
        strcpy(ent->pool, pool);
        strcpy(ent->name, name);
        ent->inited = 0;

        *_ent = ent;

        return 0;
err_ret:
        return ret;
}

int __stor_root_del(const char *pool)
{
        int ret;
        struct list_head *pos, *n;
        root_entry_t *ent;
        root_t *root = __root__;

        DINFO("pool %s root %p\n", pool, root);

        if (root == NULL) {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

        ret = sy_rwlock_wrlock(&root->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        list_for_each_safe(pos, n, &root->list) {
                ent = (root_entry_t *)pos;

                if (!strcmp(ent->pool, pool)) {
                        list_del(&ent->hook);
                        yfree((void **)&ent);
                }
        }

        sy_rwlock_unlock(&root->lock);

        return 0;
err_ret:
        return ret;
}

static int __stor_root_get(const char *pool, const char *name, fileid_t *rootid)
{
        int ret;
        root_t *root = __root__;

        if (root == NULL) {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

        ret = __stor_root_find(root, pool, name, rootid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int stor_root(const char *pool, const char *name, fileid_t *rootid)
{
        int ret;
        root_t *root = __root__;

        if (root == NULL) {
                ret = EAGAIN;
                GOTO(err_ret, ret);
        }

        DBUG("root: %s %s\n", pool, name);
        ret = __stor_root_get(pool, name, rootid);
        if (unlikely(ret)) {
                if (ret == ENOENT) {
                        ret = __stor_root_load(pool, name, rootid);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                } else
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int stor_root_init()
{
        int ret;
        root_t *root;

        ret = ymalloc((void **)&root, sizeof(*root));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        memset(root, 0x0, sizeof(*root));

        ret = sy_rwlock_init(&root->lock, "stor_root.lock");
        if (unlikely(ret))
                GOTO(err_free, ret);

        INIT_LIST_HEAD(&root->list);

        __root__ = root;

        return 0;
err_free:
        yfree((void **)&root);
err_ret:
        return ret;
}

int stor_root_del(const char *pool)
{
        int ret;

        //先删除内存结构，在清理磁盘前会不会又被加载起来，
        //造成一直在内存里？

        // 清除pool/iscsi等
        ret = __stor_root_del(pool);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        // 清除pool的内存结构和磁盘结构
        ret = md_root_del(pool);
        if (unlikely(ret))
                GOTO(err_ret, ret);


        return 0;
err_ret:
        return ret;
}
